package com.kming.crypto;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.EscapeUtil;
import com.kming.crypto.annotation.DecryptBody;
import com.kming.crypto.config.CryptoConfig;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Optional;

/**
 * requestBody数据解密，如果类或方法上有@DecryptBody注解的话
 * 2022/5/13 8:42
 */
@ControllerAdvice
public class DecryptRequestBody extends RequestBodyAdviceAdapter {

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 判断方法或类上是否有@DecryptBody注解
        return methodParameter.hasMethodAnnotation(DecryptBody.class)
                || methodParameter.getContainingClass().isAnnotationPresent(DecryptBody.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        // 从方法或类上获取@DecryptBody注解
        DecryptBody decrypt = Optional
                .ofNullable(parameter.getMethodAnnotation(DecryptBody.class))
                .orElse(parameter.getContainingClass().getAnnotation(DecryptBody.class));
        InputStream inputStream = CryptoConfig.getDecrypt(decrypt.value()).decrypt(getBodyStr(inputMessage));
        HttpInputMessage message = new DecryptHttpInputMessage(inputStream, inputMessage.getHeaders());
        return super.beforeBodyRead(message, parameter, targetType, converterType);
    }

    protected String getBodyStr(HttpInputMessage inputMessage) throws IOException {
        String body = IoUtil.read(inputMessage.getBody(), Charset.defaultCharset());
        List<String> contentTypeList = inputMessage.getHeaders().get("content-type");
        if(contentTypeList!=null
                && contentTypeList.stream().anyMatch(type->type.contains("application/x-www-form-urlencoded;"))){
            // 请求内容类型是application/x-www-form-urlencoded需要进行转义
            body = EscapeUtil.unescape(body);
        }
        return body;
    }

    static class DecryptHttpInputMessage implements HttpInputMessage {
        private final InputStream body;
        private final HttpHeaders headers;

        public DecryptHttpInputMessage(InputStream body, HttpHeaders headers) {
            this.body = body;
            this.headers = headers;
        }

        @Override
        public InputStream getBody() {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }
}
